#!/usr/bin/env ruby

require 'panoptimon/util'

require 'json'
class Array; def to_h; Hash[self]; end; end

opt = JSON::parse(ARGV[0], {symbolize_names: true})

pgrep = ['pgrep']

checks = opt[:checks]

# pgrep each check to gather pids, then ps those pids to get info

# could match a process with more than one check, so keep track
names = Hash.new {|h,k| h[k] = []}
found = {}
checks.each {|n,o|
  args = [
    o[:user] ? ['-u', o[:user]] : [],
    o[:full] ? ['-f', o[:full]] : o[:pattern] ? o[:pattern] : []
  ].flatten
  raise "must have some process characteristic for #{n}" \
    unless args.length > 0
  cmd = pgrep + ['-d,'] + args
  warn "#{n} => #{cmd}" if opt[:debug]
  got = begin; IO.popen(cmd).readline.chomp.split(/,/); rescue; []; end
  got.reject! {|pid| pid.to_i == $$} # don't find myself
  got.each {|pid| names[pid].push(n)}
  warn "#{n} => #{got.inspect}" if opt[:debug]
  found[n] = {count: got.length}
}

ps = ['ps']
fields = %w(pid time etime pcpu nice pri vsz rss args)

# TODO discover whether our ps supports it -- and do that efficiently?
fields.push('nlwp') if Panoptimon::Util.os == :linux

fieldcount = fields.length

convert = ->(){
  to_i = ->(x) {x.to_i}; to_f = ->(x) {x.to_f};
  to_sec = ->(x) { d = 0; d = $1 if x.sub!(/^\d+-/, '');
    (h,h,m,s) = x.match(/^(?:(\d+):)?(\d+):(\d+)/).to_a
    h ||= 0
    d.to_i * 24 * 60**2 + h.to_i * 60**2 + m.to_i * 60 + s.to_i}
  {
    pcpu: to_f, time: to_sec, etime: to_sec, pid: to_i,
    nlwp: to_i, pri: to_i, rss: to_i, nice: to_i, vsz: to_i
  }
}[]

# ps gives us one row per pid => collect into `found` hash
IO.popen(ps + ['-o', fields.join(','), '-p', names.keys.join(',')]) {
  |io| io.readlines
}.drop(1).each {|l|
    row = ->(){
      f = l.chomp.sub(/^\s+/, '').split(/\s+/, fieldcount)
      (0..fieldcount-1).map {|i| [fields[i].to_sym, f[i]]}.to_h
    }[]
    pid = row[:pid]
    row[:command] = row.delete(:args)
    info = %w(command).map {|k| [k, row.delete(k.to_sym)]}.to_h
    convert.each {|k,func|
      row[k] = func.call(row[k]) if row.include?(k)}
    names[pid].each {|n|
      i = found[n][:i] ||= 0; found[n][:i] += 1
      found[n][i] = row
      _info = found[n][:_info] ||= Hash.new {|h,k| h[k] = []}
      info.each {|k,v| _info[k].push(v)}
    }
  } if names.keys.length > 0
exit $?.exitstatus unless $?.success?

found.each {|k,v| v.delete(:i)} # cleanup

puts JSON::generate(found)
